﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects.DataClasses;
using Fujitsu.JiraAnalysis.JiraRPC;
using Fujitsu.JiraAnalysis.DAL;
using Fujitsu.JiraAnalysis.Common;
using Fujitsu.JiraAnalysis.TrackingSystemAdapter;

namespace Fujitsu.JiraAnalysis.ReplicationService.Synchronize
{
    class ProjectIssueUpdate : DbObjectUpdateBase
    {
        private IssueStorage issueStorage = new IssueStorage();
        private FixVersionsUpdate fixVersionUpdate = new FixVersionsUpdate();

        public ProjectIssueUpdate(ITrackSystemInfo adapter, int projectId, string projectKey, string projectName)
            : base(adapter, projectId, projectKey, projectName)
        { }

        #region Abstract
        protected override IEnumerable<AbstractRemoteEntity> GetRemoteEntities(string projectKey)
        {
            return Adapter.GetIssuesForProject(projectKey, false);
        }

        protected override EntityObject FindMatchInDb(int projectId, AbstractRemoteEntity remoteEntity)
        {
            return issueStorage.GetIssue(projectId, (remoteEntity as RemoteIssue).key);
        }

        protected override void UpdateEntityObject(EntityObject dbEntityObject, AbstractRemoteEntity remoteEntity)
        {
            var dbIssue = dbEntityObject as Issue;
            var jiraIssue = remoteEntity as RemoteIssue;

            logInfo.AppendLine(string.Format("Project '{0}' -> updated issue: '{1}', updated fields are", Name, dbIssue.IssueKey));

            var isIssueEdited = UpdateIssueReporter(dbIssue, jiraIssue)

             | UpdateIssueAssignee(dbIssue, jiraIssue)

            //Type------------------------------------------------------------------
             | UpdateIssueType(dbIssue, jiraIssue.type)

            //Summary-----------------------------------------------------------------
             | UpdateEntityObjectField<string>(dbIssue, "Summary", dbIssue.Summary, jiraIssue.summary)

            //Description-------------------------------------------------------------
             | UpdateEntityObjectField<string>(dbIssue, "Description", dbIssue.Description, jiraIssue.description)

            //Priority-----------------------------------------------------------------
             | UpdateIssuePriority(dbIssue, jiraIssue.priority)

            //Resolution---------------------------------------------------------------
             | UpdateIssueResolution(dbIssue, jiraIssue.resolution)

            //Status-------------------------------------------------------------------
             | UpdateIssueStatus(dbIssue, jiraIssue.status)

            //Created------------------------------------------------------------------
             | UpdateEntityObjectField<DateTime?>(dbIssue, "Created", dbIssue.Created, jiraIssue.created)

            //Updated------------------------------------------------------------------
             | UpdateEntityObjectField<DateTime?>(dbIssue, "Updated", dbIssue.Updated, jiraIssue.updated)

            //Duedate------------------------------------------------------------------
             | UpdateEntityObjectField<DateTime?>(dbIssue, "Duedate", dbIssue.Duedate, jiraIssue.duedate)

            //FixVersion
            | UpdateFixVersions(dbIssue, jiraIssue);


            if (isIssueEdited)
            {
                issueStorage.UpdateIssue(dbIssue);
                EventLogger.CreateInstance(eventLog).LogWrite(logInfo.ToString());
            }

            UpdateWorklogs(dbIssue.ID, dbIssue.IssueKey);

            logInfo.Clear();
        }

        protected override void AddEntityObjectToDb(int projectId, AbstractRemoteEntity remoteEntityToBeAdded)
        {
            var jiraIssue = remoteEntityToBeAdded as RemoteIssue;

            var dbIssue = new Issue { IssueKey = jiraIssue.key, ProjectID = projectId };

            var issueReporter = Adapter.GetUser(jiraIssue.reporter);
            dbIssue.ReporterID = userStorage.AddOrUpdateUser(issueReporter.name, issueReporter.name, issueReporter.fullname);

            var issueAssigneeId = Adapter.GetUser(jiraIssue.assignee);
            dbIssue.AssigneeID = userStorage.AddOrUpdateUser(issueAssigneeId.name, issueAssigneeId.name, issueAssigneeId.fullname);

            dbIssue.TypeID = issueStorage.GetTypeId(projectId, int.Parse(jiraIssue.type));
            dbIssue.PriorityID = issueStorage.GetPriorityId(projectId, int.Parse(jiraIssue.priority));
            int resolutionInt;
            int.TryParse(jiraIssue.resolution, out resolutionInt);
            dbIssue.ResolutionID = issueStorage.GetResolutionId(projectId, resolutionInt);
            dbIssue.StatusID = issueStorage.GetStatusId(projectId, int.Parse(jiraIssue.status));

            dbIssue.Summary = jiraIssue.summary;
            dbIssue.Description = jiraIssue.description;

            dbIssue.Created = jiraIssue.created;
            dbIssue.Updated = jiraIssue.updated;
            dbIssue.Duedate = jiraIssue.duedate;

            if (issueStorage.AddIssue(dbIssue) != null)
            {
                logInfo.AppendLine(string.Format("Added issue '{0}' to project '{1}'", dbIssue.IssueKey, Name));
                UpdateFixVersions(dbIssue, jiraIssue);
                EventLogger.CreateInstance(eventLog).LogWrite(logInfo.ToString());
                UpdateWorklogs(dbIssue.ID, dbIssue.IssueKey);

                logInfo.Clear();
            }
        }

        protected override IEnumerable<int> GetToBeDeletedList(int projectId, IEnumerable<AbstractRemoteEntity> remoteEntities)
        {
            var jiraIssueKeys = remoteEntities.Select(i => (i as RemoteIssue).key).ToList();
            return issueStorage.GetIssueIdsToBeDeleted(projectId, jiraIssueKeys);
        }

        protected override void DeleteEntityObject(int dbEntityObjectId)
        {
            issueStorage.DeleteIssue(dbEntityObjectId);
        }
        #endregion

        #region Update users
        private bool UpdateIssueReporter(Issue dbIssue, RemoteIssue jiraIssue)
        {
            var result = UpdateRelatedUser(dbIssue.ReporterID, jiraIssue.reporter);
            if (result != null)
            {
                return UpdateEntityObjectField<int>(dbIssue, "ReporterID", dbIssue.ReporterID, result.Value);
            }
            return false;
        }

        private bool UpdateIssueAssignee(Issue dbIssue, RemoteIssue jiraIssue)
        {
            var result = UpdateRelatedUser(dbIssue.AssigneeID, jiraIssue.assignee);
            return result != null && UpdateEntityObjectField<int>(dbIssue, "AssigneeID", dbIssue.AssigneeID, result.Value);
        }
        #endregion

        private bool UpdateFixVersions(Issue dbIssue, RemoteIssue jiraIssue)
        {
            var message = fixVersionUpdate.Update(dbIssue, jiraIssue);
            if (!string.IsNullOrEmpty(message))
            {
                logInfo.AppendLine(string.Concat("\tAdded FixVersions: ", message));
                return true;
            }
            return false;
        }

        private void UpdateWorklogs(int issueId, string issueKey)
        {
            var issueWorklogUpdate = new IssueWorklogUpdate(Adapter, issueId, issueKey, issueKey);
            issueWorklogUpdate.Update();
        }

        #region Update other fields
        private bool UpdateIssueType(Issue dbIssue, string type)
        {
            return UpdateEntityObjectField<int>(dbIssue, "TypeID", dbIssue.TypeID, issueStorage.GetTypeId(dbIssue.ProjectID, int.Parse(type)));
        }

        private bool UpdateIssuePriority(Issue dbIssue, string priority)
        {
            return UpdateEntityObjectField<int>(dbIssue, "PriorityID", dbIssue.PriorityID, issueStorage.GetPriorityId(dbIssue.ProjectID, int.Parse(priority)));
        }

        private bool UpdateIssueResolution(Issue dbIssue, string resolution)
        {
            int resolutionInt;
            int.TryParse(resolution, out resolutionInt);
            return UpdateEntityObjectField<int?>(dbIssue, "ResolutionID", dbIssue.ResolutionID, issueStorage.GetResolutionId(dbIssue.ProjectID, resolutionInt));
        }

        private bool UpdateIssueStatus(Issue dbIssue, string status)
        {
            return UpdateEntityObjectField<int>(dbIssue, "StatusID", dbIssue.StatusID, issueStorage.GetStatusId(dbIssue.ProjectID, int.Parse(status)));
        }
        #endregion
    }
}
